package com.leps.nq.sys.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leps.nq.bu.entity.*;
import com.leps.nq.bu.mapper.*;
import com.leps.nq.common.SysConsts;
import com.leps.nq.common.exception.BadCredentialsException;
import com.leps.nq.common.exception.NqFileUploadException;
import com.leps.nq.common.exception.UserNameAlreadyInUseException;
import com.leps.nq.common.util.IpUtils;
import com.leps.nq.common.util.SecurityUtil;
import com.leps.nq.common.util.SpringUtils;
import com.leps.nq.common.vo.PageVo;
import com.leps.nq.sys.entity.SysMenu;
import com.leps.nq.sys.entity.SysRole;
import com.leps.nq.sys.entity.SysUser;
import com.leps.nq.sys.entity.SysUserRole;
import com.leps.nq.sys.mapper.SysMenuMapper;
import com.leps.nq.sys.mapper.SysRoleMapper;
import com.leps.nq.sys.mapper.SysUserMapper;
import com.leps.nq.sys.mapper.SysUserRoleMapper;
import com.leps.nq.sys.service.ISysUserService;
import com.leps.nq.sys.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author hex
 * @since 2022-05-20
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysMenuMapper menuMapper;

    @Autowired
    private SysRoleMapper roleMapper;

    @Autowired
    private SysUserRoleMapper userRoleMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private BuUserCustMapper buUserCustMapper;

    @Autowired
    private BuUserAreaMapper buUserAreaMapper;

    @Autowired
    private BuUserStationMapper buUserStationMapper;

    @Autowired
    private BuTeamMapper buTeamMapper;

    @Autowired
    private BuCustMapper custMapper;

    @Autowired
    private BuAreaMapper areaMapper;

    @Autowired
    private BuStationMapper stationMapper;

    @Override
    public PageVo<SysUser> selectUserListPage(UserQueryModel queryModel) {
        LambdaQueryChainWrapper<SysUser> w = new LambdaQueryChainWrapper<>(sysUserMapper)
                .select(SysUser.class, f -> !"password".equals(f.getField().getName()))
                .eq(queryModel.getUserId() != null, SysUser::getUserId, queryModel.getUserId())
                .eq(queryModel.getStatus() != null, SysUser::getStatus, queryModel.getStatus())
                .like(queryModel.getUserName() != null, SysUser::getUserName, queryModel.getUserName())
                .like(queryModel.getPhonenumber() != null, SysUser::getPhonenumber, queryModel.getPhonenumber())
                .orderByAsc(SysUser::getUserId);
        Page<SysUser> page = w.page(Page.of(queryModel.getPageNum(), queryModel.getPageSize()));
        return PageVo.of(page);
    }

    @Override
    public SysUser selectUserByUserName(String userName) {
        return new LambdaQueryChainWrapper<SysUser>(sysUserMapper)
                .eq(SysUser::getUserName, userName)
                .one();
    }

    @Override
    public List<SysRole> selectRolesByUserId(Long userId) {
        return this.roleMapper.selectRolesByUserId(userId);
    }

    @Override
    public List<MenuInfoVo> selectMenusByUserId(Long userId) {
        List<SysMenu> sysMenus = menuMapper.selectMenuListByUserId(userId);
        List<MenuInfoVo> tree = this.buildRootMenus(sysMenus);
        fillSubMenus(tree, sysMenus);
        return tree;
    }

    private void fillSubMenus(List<MenuInfoVo> parents, List<SysMenu> sysMenus) {
        for (MenuInfoVo parent : parents) {
            List<MenuInfoVo> subMenus = new ArrayList<>();
            for (SysMenu sysMenu : sysMenus) {
                if (sysMenu.getParentId() == parent.getMenuId()) {
                    MenuInfoVo sub = new MenuInfoVo();
                    BeanUtils.copyProperties(sysMenu, sub);
                    subMenus.add(sub);
                }
            }
            parent.setChildren(subMenus);
            fillSubMenus(subMenus, sysMenus);
        }
    }

    private List<MenuInfoVo> buildRootMenus(List<SysMenu> sysMenus) {
        List<MenuInfoVo> rootMenus = new ArrayList<>();
        for (SysMenu sysMenu : sysMenus) {
            if (sysMenu.getParentId() == 0L) {
                MenuInfoVo menuInfoVo = new MenuInfoVo();
                BeanUtils.copyProperties(sysMenu, menuInfoVo);
                rootMenus.add(menuInfoVo);
            }
        }
        return rootMenus;
    }

    @Override
    public List<String> selectPermsByUserId(Long userId) {
//        if (SecurityUtil.isAdmin()) {
//            return menuMapper.selectMenuPerms();
//        }
        return menuMapper.selectMenuPermsByUserId(userId);
    }

    @Override
    public boolean changeStatus(Long userId, String status) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setStatus(status);
        sysUser.setUpdateBy(SecurityUtil.getUserName());
        int rows = this.sysUserMapper.updateUser(sysUser);
        return rows > 0;
    }

    @Override
    @Transactional
    public boolean grantRoles(Long userId, Long[] roleIds) {
        this.userRoleMapper.deleteUserRoleByUserId(userId);
        List<SysUserRole> sysUserRoles = Arrays.stream(roleIds).map(roleId -> {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(userId);
            sysUserRole.setRoleId(roleId);
            return sysUserRole;
        }).collect(Collectors.toList());
        int rows = this.userRoleMapper.batchUserRole(sysUserRoles);
        return rows > 0;
    }

    @Override
    public boolean updateUser(SysUser user) {
        user.setUpdateBy(SecurityUtil.getUserName());
        String password = user.getPassword();
        if(StrUtil.isNotEmpty(password))
            user.setPassword(BCrypt.hashpw(user.getPassword()));
        int rows = this.sysUserMapper.updateUser(user);
        return rows > 0;
    }

    @Override
    public boolean resetPwd(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setPassword(SysConsts.SYS_DEFAULT_PASSWORD_HASHED);
        sysUser.setUpdateBy(SecurityUtil.getUserName());
        int rows = this.sysUserMapper.updateUser(sysUser);
        return rows > 0;
    }

    @Override
    public boolean changePwdBySelf(String oldPwd, String newPwd) {
        // get current user
        Long userId = SecurityUtil.getUserId();
        SysUser sysUser = sysUserMapper.selectById(userId);
        // check oldPwd
        boolean oldPwdCorrect = BCrypt.checkpw(oldPwd, sysUser.getPassword());
        if (!oldPwdCorrect) throw new BadCredentialsException();
        // hash newpwd
        String hashedNewPwd = BCrypt.hashpw(newPwd);
        sysUser.setPassword(hashedNewPwd);
        sysUser.setUpdateBy(SecurityUtil.getUserName());
        // update
        int rows = this.sysUserMapper.updateUser(sysUser);
        return rows > 0;
    }

    @Override
    @Transactional
    public boolean createUser(CreateUserVo createUserVo) throws NqFileUploadException, UserNameAlreadyInUseException {
        // check userName unique
        String userName = createUserVo.getUserName();
        int unique = this.sysUserMapper.checkUserNameUnique(userName);
        if (unique > 0) {
            throw new UserNameAlreadyInUseException("此帐号已被他人使用：" + userName);
        }
        // prepare entity
        SysUser sysUser = BeanUtil.toBean(createUserVo, SysUser.class);
        // save user
        sysUser.setPassword(BCrypt.hashpw(sysUser.getPassword()));
        sysUser.setCreateBy(SecurityUtil.getUserName());
        sysUserMapper.insertUser(sysUser);
        Long userId = sysUser.getUserId();
        // save user_role
        List<Long> roleIds = createUserVo.getRoleIds();
        if (ObjectUtil.isNotEmpty(roleIds)) {
            List<SysUserRole> sysUserRoles = roleIds.stream().map(roleId -> {
                SysUserRole sysUserRole = new SysUserRole();
                sysUserRole.setRoleId(roleId);
                sysUserRole.setUserId(userId);
                return sysUserRole;
            }).collect(Collectors.toList());
            this.sysUserRoleMapper.batchUserRole(sysUserRoles);
        }
        // save user_cust
        List<Integer> custIds = createUserVo.getCustIds();
        if (ObjectUtil.isNotEmpty(custIds)) {
            for (Integer custId : custIds) {
                BuUserCust buUserCust = new BuUserCust();
                buUserCust.setUserId(userId);
                buUserCust.setCustId(custId);
                this.buUserCustMapper.insert(buUserCust);
            }
        }
        // save user_area
        List<Integer> areaIds = createUserVo.getAreaIds();
        if (ObjectUtil.isNotEmpty(areaIds)) {
            for (Integer areaId : areaIds) {
                BuUserArea userArea = new BuUserArea();
                userArea.setUserId(userId);
                userArea.setAreaId(areaId);
                this.buUserAreaMapper.insert(userArea);
            }
        }
        // save user_station
        List<Integer> stationIds = createUserVo.getStationIds();
        if (ObjectUtil.isNotEmpty(stationIds)) {
            for (Integer stationId : stationIds) {
                BuUserStation userStation = new BuUserStation();
                userStation.setUserId(userId);
                userStation.setStationId(stationId);
                this.buUserStationMapper.insert(userStation);
            }
        }
        return true;
    }

    @Override
    public UserInfoVo selectDetailsById(Long userId) {
        // base info
        SysUser sysUser = this.sysUserMapper.selectById(userId);
        // roles
        List<SysRole> roles = this.roleMapper.selectRolesByUserId(userId);
        // menus
        List<MenuInfoVo> menus = this.selectMenusByUserId(userId);
        // perms
        List<String> perms = this.menuMapper.selectMenuPermsByUserId(userId);
        // custs
        List<BuCust> custs = this.custMapper.selectCustListByUserId(userId);
        // areas
        List<BuArea> areas = this.areaMapper.selectAreaListByUserId(userId);
        // stations
        List<BuStation> stations = this.stationMapper.selectStationListByUserId(userId);
        // return
        UserInfoVo userInfoVo = BeanUtil.toBean(sysUser, UserInfoVo.class);
        userInfoVo.setRoles(roles);
        userInfoVo.setMenus(menus);
        userInfoVo.setPerms(perms);
        userInfoVo.setCusts(custs);
        userInfoVo.setAreas(areas);
        userInfoVo.setStations(stations);
        return userInfoVo;
    }

    @Override
    public void updateLoginInfo() {
        HttpServletRequest request = SpringUtils.getRequest();
        String loginIp = IpUtils.getIpAddr(request);
        Date loginDate = new Date();
        Long userId = SecurityUtil.getUserId();
        this.sysUserMapper.updateLoginInfo(userId, loginIp, loginDate);
    }

    @Override
    @Transactional
    public boolean grantRoles2(GrantRoles2Vo vo) {
        // 根据不同角色按不同策略计算数据权限数据
        this.prepareDataPermissionData(vo);
        // 准备好的数据权限数据
        Long userId = vo.getUserId();
        List<Long> roleIds = vo.getRoleIds();
        List<Integer> custIds = vo.getCustIds();
        List<Integer> areaIds = vo.getAreaIds();
        List<Integer> stationIds = vo.getStationIds();
        // 删除原有的sys_user_role
        this.userRoleMapper.deleteUserRoleByUserId(userId);
        // 插入sys_user_role
        if (ObjectUtil.isNotEmpty(roleIds)) {
            List<SysUserRole> userRoles = roleIds.stream().map(roleId -> {
                SysUserRole userRole = new SysUserRole();
                userRole.setUserId(userId);
                userRole.setRoleId(roleId);
                return userRole;
            }).collect(Collectors.toList());
            this.userRoleMapper.batchUserRole(userRoles);
        }
        // 删除原有的bu_user_cust
        this.buUserCustMapper.deleteByUserId(userId);
        // 插入bu_user_cust
        if (ObjectUtil.isNotEmpty(custIds))
            this.buUserCustMapper.insertBatch(userId, custIds);
        // 删除原有的bu_user_area
        this.buUserAreaMapper.deleteByUserId(userId);
        // 插入bu_user_area
        if (ObjectUtil.isNotEmpty(areaIds))
            this.buUserAreaMapper.insertBatch(userId, areaIds);
        // 删除原有的 bu_user_station
        this.buUserStationMapper.deleteByUserId(userId);
        // 插入bu_user_station
        if (ObjectUtil.isNotEmpty(stationIds))
            this.buUserStationMapper.insertBatch(userId, stationIds);
        return true;
    }

    /**
     * [important]根据角色计算数据权限数据
     */
    private void prepareDataPermissionData(GrantRoles2Vo vo) {
        List<Long> roleIds = vo.getRoleIds();
        List<Integer> custIds = vo.getCustIds();
        List<Integer> areaIds = vo.getAreaIds();
        List<Integer> stationIds = vo.getStationIds();
        // cache
        Set<Integer> tmpCustIds = new HashSet<>();
        Set<Integer> tmpAreaIds = new HashSet<>();
        Set<Integer> tmpStationIds = new HashSet<>();
        tmpCustIds.addAll(custIds);
        tmpAreaIds.addAll(areaIds);
        tmpStationIds.addAll(stationIds);
        // 超级管理员
        if (roleIds.contains(SysConsts.ROLE_SUPER_ADMIN_ID)) {
            // All cust
            LambdaQueryChainWrapper<BuCust> wrapper = new LambdaQueryChainWrapper<BuCust>(this.custMapper).select(BuCust::getCustId);
            custIds = wrapper.list().stream().map(item -> item.getCustId()).collect(Collectors.toList());
            vo.setCustIds(custIds);
            // All area
            areaIds = this.areaMapper.selectList(null).stream().map(item -> item.getAreaId()).collect(Collectors.toList());
            vo.setAreaIds(areaIds);
            // All station
            stationIds = this.stationMapper.selectList(null).stream().map(item -> item.getStationId()).collect(Collectors.toList());
            vo.setStationIds(stationIds);
            // completed
            return;
        }
        // 平台级管理员
        if (roleIds.contains(SysConsts.ROLE_STATION_ADMIN_ID)) {
            // Require at least one stationId
            if (ObjectUtil.isEmpty(tmpStationIds)) {
                throw new RuntimeException("请至少为平台级管理员分配一个平台");
            }
            // Get relational custIds and areaIds
            List<BuStation> stations = this.stationMapper.selectList(new QueryWrapper<BuStation>().in("station_id", tmpStationIds));
            Set<Integer> relationalAreaIds = stations.stream().map(item -> item.getAreaId()).collect(Collectors.toSet());
            Set<Integer> relationalCustIds = stations.stream().map(item -> item.getCustId()).collect(Collectors.toSet());
            tmpAreaIds.addAll(relationalAreaIds);
            tmpCustIds.addAll(relationalCustIds);
        }
        // 区块级管理员
        if (roleIds.contains(SysConsts.ROLE_AREA_ADMIN_ID)) {
            // Require at least one areaId
            if (ObjectUtil.isEmpty(tmpAreaIds)) {
                throw new RuntimeException("请至少为区块级管理员分配一个区块");
            }
            // Get relational stationIds
            List<Integer> list = this.stationMapper.selectStationIdListByAreaId(tmpAreaIds);
            tmpStationIds.addAll(list);
            // Get relational custIds
            List<BuArea> areas = this.areaMapper.selectList(new QueryWrapper<BuArea>().in("area_id", tmpAreaIds));
            Set<Integer> relationalCustIds = areas.stream().map(item -> item.getCustId()).collect(Collectors.toSet());
            tmpCustIds.addAll(relationalCustIds);
        }
        // 客户级管理员
        if (roleIds.contains(SysConsts.ROLE_CUST_ADMIN_ID)) {
            // Require at least one custId
            if (ObjectUtil.isEmpty(tmpCustIds)) {
                throw new RuntimeException("请至少为客户级管理员分配一个客户");
            }
            // Get relational areaIds and stationIds
            List<Integer> list = this.areaMapper.selectAreaIdListByCustId(tmpCustIds);
            tmpAreaIds.addAll(list);
            if (ObjectUtil.isNotEmpty(tmpAreaIds)) {
                list = this.stationMapper.selectStationIdListByAreaId(tmpAreaIds);
                tmpStationIds.addAll(list);
            }
        }
        // 客户帐号
        if (roleIds.contains(SysConsts.ROLE_CUST_USER_ID)) {
            // Require at least one custId
            if (ObjectUtil.isEmpty(tmpCustIds)) {
                throw new RuntimeException("请至少为客户帐号分配一个客户");
            }
            // Get relational areaIds and stationIds
            List<Integer> list = this.areaMapper.selectAreaIdListByCustId(tmpCustIds);
            tmpAreaIds.addAll(list);
            if (ObjectUtil.isNotEmpty(tmpAreaIds)) {
                list = this.stationMapper.selectStationIdListByAreaId(tmpAreaIds);
                tmpStationIds.addAll(list);
            }
        }

        // convert cache set to list
        custIds = CollUtil.list(false, tmpCustIds);
        areaIds = CollUtil.list(false, tmpAreaIds);
        stationIds = CollUtil.list(false, tmpStationIds);
        vo.setCustIds(custIds);
        vo.setAreaIds(areaIds);
        vo.setStationIds(stationIds);
    }
}
